#ifndef BOX2DWIDGET_H
#define BOX2DWIDGET_H

#include <QWidget>
#include <QVector2D>
#include <QBrush>
#include <QPen>
#include "box2dengine.h"

class QtBox2DBody;

class QtBox2DContactListener : public QObject, public b2ContactListener
{
    Q_OBJECT

public:
    explicit QtBox2DContactListener(QObject *parent = 0);
    void BeginContact(b2Contact *contact);
    inline void addBody(QtBox2DBody *body) { m_bodies.push_back(body); }

signals:
    void contact(QtBox2DBody*, QtBox2DBody*);

private:
    QList<QtBox2DBody *> m_bodies;
};

class QGraphicsScene;
class b2Body;
class b2Shape;
class b2Fixture;
class b2MouseJoint;
class QtBox2DEngine;
class QtBox2DFixture;

class QtBox2DFixture
{
public:
    enum Box2DShapeType
    {
        CircleShape = 0,
        EdgeShape,
        PolygonShape,
        ChainShape
    };

    explicit QtBox2DFixture(b2Fixture *fixture);
    ~QtBox2DFixture();
    Box2DShapeType shapeType() const;
    inline void setFriction(float32 friction) { m_fixture->SetFriction(friction); }
    inline float32 friction() const { return m_fixture->GetFriction(); }
    inline void setDensity(float32 density) { m_fixture->SetDensity(density); }
    inline float32 density() const { return m_fixture->GetDensity(); }
    inline void setRestitution(float32 restitution) { m_fixture->SetRestitution(restitution); }
    inline float32 restitution() const { return m_fixture->GetRestitution(); }
    inline void setVisible(bool visible) { m_visible = visible; }
    inline bool isVisible() const { return m_visible; }
    inline const b2Shape *shape() const { return m_fixture->GetShape(); }
    inline void setBrush(const QBrush &brush) { m_brush = brush; }
    inline QBrush brush() const { return m_brush; }
    inline void setPen(const QPen &pen) { m_pen = pen; }
    inline QPen pen() const { return m_pen; }

private:
    Q_DISABLE_COPY(QtBox2DFixture)

    b2Fixture *const m_fixture;
    bool       m_visible;
    QBrush     m_brush;
    QPen       m_pen;
};

class QtBox2DBody
{
public:
    enum Box2DBodyType
    {
        StaticBody = 0,
        KinematicBody,
        DynamicBody
    };

    enum Box2dBodyPaintFlag
    {
        PaintRotation = 0x01,
        DebugDraw     = 0x02
    };

    QtBox2DBody(QtBox2DEngine *engine, Box2DBodyType type);
    ~QtBox2DBody();
    inline QList<QtBox2DFixture *> fixtures() const { return m_fixtures; }
    void setPosition(qreal x, qreal y);
    inline QPointF position() const { return QPointF(m_body->GetPosition().x, -m_body->GetPosition().y); }
    void setRotation(qreal r);
    qreal rotation() const;
    void setType(Box2DBodyType type);
    inline void setActive(bool flag) { m_body->SetActive(flag); }
    inline bool isActive() const { return m_body->IsActive(); }
    inline void setAngularDamping(float32 damping) { m_body->SetAngularDamping(damping); }
    inline float32 angularDamping() const { return m_body->GetAngularDamping(); }
    inline void setAngularVelocity(float32 velocity) { m_body->SetAngularVelocity(velocity); }
    inline float32 angularVelocity() const { return m_body->GetAngularVelocity(); }
    inline void setBullet(bool flag) { m_body->SetBullet(flag); }
    inline bool isBullet() const { return m_body->IsBullet(); }
    inline void setLinearDamping(float32 damping) { m_body->SetLinearDamping(damping); }
    inline float32 linearDamping() const { return m_body->GetLinearDamping(); }
    inline void setLinearVelocity(float32 x, float32 y) { m_body->SetLinearVelocity(b2Vec2(x, y)); }
    inline QVector2D linearVelocity() const { const b2Vec2 &v = m_body->GetLinearVelocity(); return QVector2D(v.x, v.y); }
    inline void applyAngularImpulse(float32 impulse) { m_body->ApplyAngularImpulse(impulse); }
    inline void applyForce(float32 x, float32 y, float32 px, float32 py) { m_body->ApplyForce(b2Vec2(x, -y), b2Vec2(px, -py)); }
    inline void applyForceToCenter(float32 x, float32 y) { m_body->ApplyForceToCenter(b2Vec2(x, y)); }
    inline void applyLinearImpulse(float32 x, float32 y, float32 px, float32 py) { m_body->ApplyLinearImpulse(b2Vec2(x, y), b2Vec2(px, py)); }
    inline void applyTorque(float32 torque) { m_body->ApplyTorque(torque); }
    inline const b2Body *body() const { return m_body; }
    void setPaintFlag(Box2dBodyPaintFlag flag, bool active = true);
    inline int flags() const { return m_flags; }
    QtBox2DFixture *createFixture(const b2Shape &shape);

private:
    Q_DISABLE_COPY(QtBox2DBody)

    QtBox2DEngine    *const m_engine;
    b2Body           *const m_body;
    QList<QtBox2DFixture *> m_fixtures;
    int                     m_flags;
};

class b2Body;
class b2ChainShape;
class b2EdgeShape;
class b2CircleShape;
class b2PolygonShape;
class b2Vec2;
class b2MouseJoint;

class QtBox2DWidget : public QWidget
{
    Q_OBJECT

public:
    explicit QtBox2DWidget(QtBox2DEngine *engine, QWidget *parent = 0);
    ~QtBox2DWidget() {}
    inline void setScale(qreal s) { m_scale = s; }
    QTransform transform() const;
    inline void setRubberbandEnabled(bool enabled) { m_bandEnabled = enabled; }

    QtBox2DFixture *createCircleFixture(QtBox2DBody *body, float radius, float x = 0, float y = 0);
    QtBox2DFixture *createEdgeFixture(QtBox2DBody *body, float x1, float y1, float x2, float y2);
    QtBox2DFixture *createPolygonFixture(QtBox2DBody *body, const QPolygonF &polygon);
    QtBox2DFixture *createChainFixture(QtBox2DBody *body, const QPolygonF &polygon);

protected:
    void paintEvent(QPaintEvent *);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void timerEvent(QTimerEvent*);

private slots:
    void createRubberband(b2Body *body);

private:
    void paintFixtures(QPainter *painter, const QtBox2DBody *body) const;
    void paintChain(QPainter *painter, const b2ChainShape *shape, b2Vec2 pos, float angle) const;
    void paintEdge(QPainter *painter, const b2EdgeShape *shape, b2Vec2 pos, float angle) const;
    void paintCircle(QPainter *painter, const b2CircleShape *shape, b2Vec2 pos, float angle) const;
    void paintPolygon(QPainter *painter, const b2PolygonShape *shape, b2Vec2 pos, float angle) const;
    void insertVertices(QPolygonF polygon, b2Vec2 *vertices);

    QtBox2DEngine *m_engine;
    b2MouseJoint  *m_mouseJoint;
    b2Body        *m_mouseBody;
    QLineF         m_line;
    QPointF        m_mpPos;
    bool           m_rubberband;
    bool           m_bandEnabled;
    int            m_timer;
    qreal          m_scale;
};

#endif // BOX2DWIDGET_H

